/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/

package smartcardjava;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Signature;
import java.security.SignatureException;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.List;
import javax.smartcardio.*;

/**
 *
 * @author rmartinezch
 */
public class SmartCardJava {
    
    /**
     * @param args the command line arguments
     */
    static Certificate certificate = null;
    static byte[] signatureValue = null;
    
    static String auth = "/windows/Projects/Certificates/auth3401.cer";
    static String sign = "/windows/Projects/Certificates/sign3402.cer";
    
//    private byte[] apSELECT_AUTHENTIC = {(byte)0x00,(byte)0xA4,(byte)0x04,(byte)0x00,(byte)0x10,(byte)0xA0,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x77,(byte)0x01,(byte)0x00,(byte)0x70,(byte)0x0A,(byte)0x10,(byte)0x00,(byte)0xF1,(byte)0x00,(byte)0x00,(byte)0x01,(byte)0x00};
//    private byte[] select_3F00 = {(byte)0x00,(byte)0xA4,(byte)0x00,(byte)0x00,(byte)0x02,(byte)0x3F,(byte)0x00};
//    private byte[] select_5015 = {(byte) 0x00, (byte) 0xA4, (byte) 0x00, (byte) 0x00, (byte) 0x02, (byte) 0x50, (byte) 0x15};
//    private byte[] select_3401 = {(byte) 0x00, (byte) 0xA4, (byte) 0x00, (byte) 0x00, (byte) 0x02, (byte) 0x34, (byte) 0x01};
//    static private byte[] HASH_msg = {(byte) 0x34, (byte) 0xA9, (byte) 0x95, (byte) 0xE1, (byte) 0xC2, (byte) 0x75, (byte) 0xA3, (byte) 0x6F, (byte) 0xB1, (byte) 0x84, (byte) 0xE4, (byte) 0xCD, (byte) 0x96, (byte) 0x64, (byte) 0xF1, (byte) 0xDA, (byte) 0x56, (byte) 0x87, (byte) 0x86, (byte) 0x81};
    static String msg = "Hello world !!!";
//    static String alg = "SHA1withRSA";
    static String alg = "SHA256withRSA";
    private void signHash() throws Exception {
        try {
            // Display the list of terminals
            TerminalFactory factory = TerminalFactory.getDefault();
            List<CardTerminal> terminals = factory.terminals().list();
            System.out.println("Terminals: " + terminals);
            
            // Use the first terminal
            CardTerminal terminal = terminals.get(0);
            
            // Connect with the card
            Card card = terminal.connect("*");
            System.out.println("card: " + card);
            CardChannel channel = card.getBasicChannel();
            
            // Send Select Applet command
            //byte[] aid = {(byte)0xA0, 0x00, 0x00, 0x00, 0x62, 0x03, 0x01, 0x0C, 0x06, 0x01};
            byte[] apSELECT_AUTHENTIC = {(byte) 0x00, (byte) 0xA4, (byte) 0x04, (byte) 0x00, (byte) 0x10, (byte) 0xA0, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x77, (byte) 0x01, (byte) 0x00, (byte) 0x70, (byte) 0x0A, (byte) 0x10, (byte) 0x00, (byte) 0xF1, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00};
            //ResponseAPDU answer = channel.transmit(new CommandAPDU(0x00, 0xA4, 0x04, 0x00, aid));
            System.out.println("select apdu : " + fromBytesToString(apSELECT_AUTHENTIC));
            ResponseAPDU answer = channel.transmit(new CommandAPDU(apSELECT_AUTHENTIC));
            System.out.println("answer 0 : " + answer.toString());
            System.out.println("data   0 : " + fromBytesToString(answer.getData()));
            
            byte[] VERIFY_PASSPHRASE = {(byte) 0x00, (byte) 0x20, (byte) 0x00, (byte) 0x01, (byte) 0x08, (byte) 0x30, (byte) 0x35, (byte) 0x30, (byte) 0x34, (byte) 0x37, (byte) 0x38, (byte) 0xFF, (byte) 0xFF};
            System.out.println("verify : " + fromBytesToString(VERIFY_PASSPHRASE));
            answer = channel.transmit(new CommandAPDU(VERIFY_PASSPHRASE));
            System.out.println("answer verify : " + answer.toString());
            
            byte[] MSE = {(byte) 0x00, (byte) 0x22, (byte) 0x41, (byte) 0xB6, (byte) 0x06, (byte) 0x80, (byte) 0x01, (byte) 0x11, (byte) 0x83, (byte) 0x01, (byte) 0x01};
            System.out.println("mse : " + fromBytesToString(MSE));
            answer = channel.transmit(new CommandAPDU(MSE));
            System.out.println("answer mse : " + answer.toString());
            
//            byte[] PSO = {(byte) 0x00, (byte) 0x2A, (byte) 0x9E, (byte) 0x9A, (byte) 0x21, (byte) 0x30, (byte) 0x1F, (byte) 0x30, (byte) 0x07, (byte) 0x06, (byte) 0x05, (byte) 0x2B, (byte) 0x0E, (byte) 0x03, (byte) 0x02, (byte) 0x1A, (byte) 0x04, (byte) 0x14, (byte) 0x34, (byte) 0xA9, (byte) 0x95, (byte) 0xE1, (byte) 0xC2, (byte) 0x75, (byte) 0xA3, (byte) 0x6F, (byte) 0xB1, (byte) 0x84, (byte) 0xE4, (byte) 0xCD, (byte) 0x96, (byte) 0x64, (byte) 0xF1, (byte) 0xDA, (byte) 0x56, (byte) 0x87, (byte) 0x86, (byte) 0x81};
            /*
            //            SHA1
            byte[] PSO = {(byte) 0x00, (byte) 0x2A, (byte) 0x9E, (byte) 0x9A, (byte) 0x21,
            (byte) 0x30, (byte) 0x1F,
            (byte) 0x30, (byte) 0x07,
            //                oid
            (byte) 0x06, (byte) 0x05, (byte) 0x2B, (byte) 0x0E, (byte) 0x03, (byte) 0x02, (byte) 0x1A,
            (byte) 0x04, (byte) 0x14,
            //               msg
            (byte) 0x2C, (byte) 0x19, (byte) 0x29, (byte) 0xC9, (byte) 0x30, (byte) 0x96, (byte) 0xFF, (byte) 0x1D, (byte) 0x62, (byte) 0x55, (byte) 0x44, (byte) 0xF7, (byte) 0x32, (byte) 0x4A, (byte) 0xC0, (byte) 0xFD, (byte) 0x77, (byte) 0x24, (byte) 0x27, (byte) 0x9C
            };
            //            */
//            byte[] PSO = {(byte) 0x00, (byte) 0x2A, (byte) 0x9E, (byte) 0x9A, (byte) 0x14, (byte) 0x34, (byte) 0xA9, (byte) 0x95, (byte) 0xE1, (byte) 0xC2, (byte) 0x75, (byte) 0xA3, (byte) 0x6F, (byte) 0xB1, (byte) 0x84, (byte) 0xE4, (byte) 0xCD, (byte) 0x96, (byte) 0x64, (byte) 0xF1, (byte) 0xDA, (byte) 0x56, (byte) 0x87, (byte) 0x86, (byte) 0x81};
///*
            // SHA-256
            byte[] PSO = {(byte) 0x00, (byte) 0x2A, (byte) 0x9E, (byte) 0x9A, (byte) 0x31,
                (byte) 0x30, (byte) 0x2F,
                (byte) 0x30, (byte) 0x0B,
                // oid
                (byte) 0x06, (byte) 0x09, (byte) 0x60, (byte) 0x86, (byte) 0x48, (byte) 0x01, (byte) 0x65, (byte) 0x03, (byte) 0x04, (byte) 0x02, (byte) 0x01,
                (byte) 0x04, (byte) 0x20,
                // hex msg hash
                (byte) 0x85, (byte) 0xD6, (byte) 0x6E, (byte) 0x4F, (byte) 0x8F, (byte) 0x9C, (byte) 0x4E, (byte) 0xA6, (byte) 0x6D, (byte) 0xF4, (byte) 0x13, (byte) 0x74, (byte) 0x97, (byte) 0x42, (byte) 0xC5, (byte) 0x19, (byte) 0x92, (byte) 0x54, (byte) 0x2E, (byte) 0x0B, (byte) 0x0E, (byte) 0x76, (byte) 0x45, (byte) 0x99, (byte) 0x82, (byte) 0x46, (byte) 0x1E, (byte) 0xE0, (byte) 0x89, (byte) 0x37, (byte) 0x77, (byte) 0xCE
            };
//             */
            
            System.out.println("pso : " + alg + " - " + fromBytesToString(PSO));
            answer = channel.transmit(new CommandAPDU(PSO));
            System.out.println("answer pso : " + answer.toString());
            
            signatureValue = answer.getData();
            System.out.println("answer pso data : " + signatureValue.length + " - " + alg + " - " + fromBytesToString(signatureValue));
            
            // Disconnect the card
            card.disconnect(false);
            
            
        } catch (Exception e) {
            System.out.println("Ouch: " + e.toString());
        }
    }
    
    private void readSmartCard() throws Exception {
        try {
            // Display the list of terminals
            TerminalFactory factory = TerminalFactory.getDefault();
            List<CardTerminal> terminals = factory.terminals().list();
            System.out.println("Terminals: " + terminals);
            
            // Use the first terminal
            CardTerminal terminal = terminals.get(0);
            
            // Connect with the card
            Card card = terminal.connect("*");
            System.out.println("card: " + card);
            CardChannel channel = card.getBasicChannel();
            
            // Send Select Applet command
            //byte[] aid = {(byte)0xA0, 0x00, 0x00, 0x00, 0x62, 0x03, 0x01, 0x0C, 0x06, 0x01};
            byte[] apSELECT_AUTHENTIC = {(byte) 0x00, (byte) 0xA4, (byte) 0x04, (byte) 0x00, (byte) 0x10, (byte) 0xA0, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x77, (byte) 0x01, (byte) 0x00, (byte) 0x70, (byte) 0x0A, (byte) 0x10, (byte) 0x00, (byte) 0xF1, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00};
            //ResponseAPDU answer = channel.transmit(new CommandAPDU(0x00, 0xA4, 0x04, 0x00, aid));
            System.out.println("select apdu : " + fromBytesToString(apSELECT_AUTHENTIC));
            ResponseAPDU answer = channel.transmit(new CommandAPDU(apSELECT_AUTHENTIC));
            System.out.println("answer 0 : " + answer.toString());
            System.out.println("data   0 : " + fromBytesToString(answer.getData()));
            
            /*
            // Send test command
            answer = channel.transmit(new CommandAPDU(0x00, 0x00, 0x00, 0x00));
            System.out.println("answer: " + answer.toString());
            byte r[] = answer.getData();
            for (int i = 0; i < r.length; i++) {
            System.out.print((char) r[i]);
            }
            */
//            System.out.println();
            
            //select 3F00
            byte[] select_3F00 = {(byte) 0x00, (byte) 0xA4, (byte) 0x00, (byte) 0x00, (byte) 0x02, (byte) 0x3F, (byte) 0x00};
            System.out.println("select MF : " + fromBytesToString(select_3F00));
            answer = channel.transmit(new CommandAPDU(select_3F00));
            if (answer.getSW() != 0x9000) {
                throw new Exception("No se logro seleccionar MF 3F00");
            }
            System.out.println("answer 1 : " + answer.toString());
            byte[] fciMF = answer.getData();
            System.out.println("data   1 : " + fromBytesToString(fciMF));
            
            //select 5015
            byte[] select_5015 = {(byte) 0x00, (byte) 0xA4, (byte) 0x00, (byte) 0x00, (byte) 0x02, (byte) 0x50, (byte) 0x15};
            System.out.println("select DF_5015 : " + fromBytesToString(select_5015));
            answer = channel.transmit(new CommandAPDU(select_5015));
            if (answer.getSW() != 0x9000) {
                throw new Exception("No se logro seleccionar EF 5015");
            }
            System.out.println("answer 2: " + answer.toString());
            byte[] dedicatedFile = answer.getData();
            System.out.println("data   2 : " + fromBytesToString(dedicatedFile));
            
            int lengthDEC;//tamanio del certificado
            
            //select EF - 3401
            byte[] select_3401 = {(byte) 0x00, (byte) 0xA4, (byte) 0x00, (byte) 0x00, (byte) 0x02, (byte) 0x34, (byte) 0x01};
            System.out.println("select EF_3401 : " + fromBytesToString(select_3401));
            answer = channel.transmit(new CommandAPDU(select_3401));
            byte[] tlvEf;// = answer.getData();
            if (answer.getSW() == 0x9000) {
//                System.out.println("TLV : " + fromBytesToString(tlvEf));
                tlvEf = answer.getData();
                //leer Tag 80 del TLV
                byte[] length3401Hex = new byte[2];
                System.arraycopy(tlvEf, 4, length3401Hex, 0, 2);
                System.out.println("length (HEX): " + fromBytesToString(length3401Hex) + ", length (DEC): " + Integer.parseInt(fromBytesToString(length3401Hex), 16));
                lengthDEC = Integer.parseInt(fromBytesToString(length3401Hex), 16);
                
            } else {
                throw new Exception("No se logro seleccionar MF 3401");
            }
            System.out.println("answer 3 : " + answer.toString());
            System.out.println("data   3 : " + fromBytesToString(tlvEf));
            
            /*
            //leer certificado  3401
            //la lectura necesariamente se realiza a travez de bucles, ya que el APDU max. devolvera 256 bytes...
            byte[] certif3401 = new byte[lengthDEC];
            byte LE = (byte) 0x00;
            for (int i = 0; i <= (lengthDEC / 256); i++) {
            if (i == (lengthDEC / 256)) {//si es ultima vuelta leer BYTES restantes
            LE = (byte) (lengthDEC % 256);//BYTES restantes
            }
            answer = channel.transmit(new CommandAPDU(
            new byte[]{(byte) 0x00, (byte) 0xB0, (byte) (i), (byte) 0x00, LE}));
            if (answer.getSW() == 0x9000) {
            if (LE == 0x00) {
            System.arraycopy(answer.getData(), 0, certif3401, (256 * i), 256);
            } else {
            System.arraycopy(answer.getData(), 0, certif3401, (256 * i), (lengthDEC % 256));
            }
            } else {
            throw new Exception("No se logro completar la lectura de bytes");
            }
            }
            
            saveFile("D:/Reniec/auth3401.cer", certif3401);
            */
            
            // write auth certificate
//            readRegistersToWriteFile(lengthDEC, channel, "D:/Reniec/auth3401.cer");
            readRegistersToWriteFile(lengthDEC, channel, auth);
            
            //select EF - 3402
            byte[] select_3402 = {(byte) 0x00, (byte) 0xA4, (byte) 0x00, (byte) 0x00, (byte) 0x02, (byte) 0x34, (byte) 0x02};
            answer = channel.transmit(new CommandAPDU(select_3402));
            if (answer.getSW() == 0x9000) {
                byte[] fci3402 = answer.getData();
                System.out.println("fci 3402 : " + fromBytesToString(fci3402));
                //leer Tag 80 del TLV
                byte[] length3402Hex = new byte[2];
                System.arraycopy(fci3402, 4, length3402Hex, 0, 2);
                System.out.println("length (HEX): " + fromBytesToString(length3402Hex) + ", length (DEC): " + Integer.parseInt(fromBytesToString(length3402Hex), 16));
                lengthDEC = Integer.parseInt(fromBytesToString(length3402Hex), 16);
                
            } else {
                throw new Exception("No se logro seleccionar MF 3402");
            }
            System.out.println("answer 6: " + answer.toString());
            
            // write sign certificate
            
//            readRegistersToWriteFile(lengthDEC, channel, "D:/Reniec/sign3402.cer");
            readRegistersToWriteFile(lengthDEC, channel, sign);
            
            // Disconnect the card
            card.disconnect(false);
        } catch (Exception e) {
            System.out.println("Ouch: " + e.toString());
        }
    }
    
    public static void main(String[] args) throws Exception {
        // TODO code application logic here
        new SmartCardJava().readSmartCard();
        /*
        new SmartCardJava().signHash();
        
        InputStream is = new FileInputStream ("D:/Reniec/RMDNIeAuth.cer");
        certificate = getCertificate(is);
        
        verify();
                */
    }
    
    static public void verify() throws NoSuchProviderException, UnrecoverableKeyException, CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
        Signature sig = Signature.getInstance(alg);
        
        sig.initVerify(certificate.getPublicKey());
        sig.update(msg.getBytes());
        
        boolean valid = sig.verify(signatureValue); // signatureValue let it by sign() function
        System.out.println("verify : " + valid);
    }
    
    static public X509Certificate getCertificate(InputStream cert) {
        X509Certificate tlCert = null;
        CertificateFactory factory = null;
        try {
            factory = CertificateFactory.getInstance("X509");
        } catch (CertificateException e) {
            System.out.println("Certificado X509 no soportado" + e.getLocalizedMessage());
            return null;
        }
        
        try {
            tlCert = (X509Certificate) factory.generateCertificate(cert);
        } catch (Exception e) {
            System.out.println("Loading certificate exception" + e.getLocalizedMessage());
        }
        return tlCert;
    }
    
    private void readRegistersToWriteFile(int length, CardChannel channel, String certificate) throws CardException, Exception {
        byte[] register = new byte[length];
        int szReg = 256;
        int numBucles = length / szReg;
        byte numBytesRes = (byte) 0x00;
        ResponseAPDU answer;
        byte[] readApdu;
        for (int i = 0; i <= numBucles; i++) {
            if (i == numBucles) {
                numBytesRes = (byte) (length % szReg);
            }
            readApdu = new byte[]{(byte) 0x00, (byte) 0xB0, (byte) i, (byte) 0x00, (byte) numBytesRes};
            System.out.println("readApdu # " + i + " : " + fromBytesToString(readApdu));
            answer = channel.transmit(new CommandAPDU(readApdu));
            if (answer.getSW() == 0x9000) {
                if (numBytesRes == 0x00) {
                    System.arraycopy(answer.getData(), 0, register, szReg * i, szReg);
                } else {
                    System.arraycopy(answer.getData(), 0, register, szReg * i, (length % szReg));
                }
            } else {
                throw new Exception("No se logró completar la lectura del registro en la vuelta " + numBucles);
            }
        }
        saveFile(certificate, register);
    }
    
    private void saveFile(String cauthcer, byte[] certif3401) throws Exception {
        FileOutputStream fos = new FileOutputStream(cauthcer);
        fos.write(certif3401);
        fos.close();
    }
    
    private String fromBytesToString(byte[] apdu) {
        String cadena = "";
        for (int i = 0; i < apdu.length; i++) {
            int b = apdu[i] & 0xFF;
            String auxa = Integer.toHexString(b).toUpperCase();
            if (auxa.length() == 1) {
                cadena = cadena + "0" + auxa;
            } else {
                cadena = cadena + auxa;
            }
        }
        return cadena;
    }
}
